package com.wechat.login;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.swak.config.setting.Settings;
import com.swak.serializer.JavaSerializer;
import com.swak.serializer.Serializer;
import com.swak.utils.FileUtils;
import com.swak.utils.Lists;
import com.swak.utils.Sets;
import com.swak.utils.StringUtils;
import com.wechat.exception.SubjectLockException;

/**
 * 登陆服务
 * 
 * @author DELL
 */
public class LoginService {
	private static Logger LOGGER = LoggerFactory.getLogger(LoginService.class);
	private static final transient Serializer SERIALIZER = new JavaSerializer();
	private static final String LOGIN = "login";
	private static final String Lock = ".lock";
	private static volatile LoginService Instance;

	private final Set<ChangeListener> changes = Sets.newHashSet();
	private final File runtimeFile;
	private final File loginFile;

	/**
	 * Login Env
	 */
	private Login login;
	private LoginLock loginLock;

	private LoginService() {
		this.runtimeFile = Settings.intSettings().getRuntimeFile();
		this.loginFile = new File(runtimeFile, LOGIN);

		try {
			if (!runtimeFile.exists()) {
				runtimeFile.mkdirs();
			}
			if (!loginFile.exists()) {
				loginFile.createNewFile();
			}
		} catch (Throwable e) {
			LOGGER.error("Init login service error {}", e);
			System.exit(-1);
		}
	}

	/**
	 * 初始化登录
	 * 
	 * @throws IOException
	 */
	public Login user() throws IOException {

		// 登录数据
		List<Login> users = this.users();

		// login one user
		Login user = null;
		for (Login login : users) {
			if (this.tryLogin(login)) {
				user = login;
				break;
			}
		}

		// create new user
		if (user == null) {
			user = this.create();
		}

		// return user
		return user.setLock(true);
	}

	private boolean isLogin(Login login) throws IOException {
		File lockFile = new File(this.runtimeFile, login.getId() + Lock);
		if (!lockFile.exists()) {
			return false;
		}
		try {
			LoginLock loginLock = new LoginLock(lockFile);
			boolean locked = loginLock.isLocked();
			loginLock.release();
			return locked;
		} catch (Throwable e) {
			return true;
		}
	}

	private boolean tryLogin(Login login) throws IOException {

		// check lock
		if (this.loginLock != null) {
			this.loginLock.release();
			this.login.setId(null).setNickName(null);
			this.loginLock = null;
		}

		// 用户ID创建唯一登录
		File lockFile = new File(this.runtimeFile, login.getId() + Lock);
		if (!lockFile.exists()) {
			lockFile.createNewFile();
		}

		// 创建登录锁
		LoginLock loginLock = new LoginLock(lockFile);

		// 是否已经登录上
		if (loginLock.isLocked()) {

			// 登录数据
			this.login = new Login();

			// 唯一的用户
			this.login.setId(login.getId()).setNickName(login.getNickName()).setHeadimg(login.getHeadimg());

			// 保留登录锁
			this.loginLock = loginLock;

			// 触发变动事件
			this.fireChangeListeners();

			// return lock result
			return true;
		}

		// return lock result
		return false;
	}

	/**
	 * 选择一个账号
	 * 
	 * @return
	 * @throws IOException
	 */
	public boolean select(String id) throws IOException {

		// 登录数据
		List<Login> users = this.users();

		// login one user
		Login user = null;
		for (Login login : users) {
			if (login.getId().equals(id)) {
				user = login;
			}
		}

		// lock user
		return this.tryLogin(user);
	}

	/**
	 * 创建新用户
	 * 
	 * @return
	 * @throws IOException
	 */
	public Login create() throws IOException {
		return this.create(NicknameGens.getNickName(), null);
	}

	/**
	 * 创建新用户
	 * 
	 * @return
	 * @throws IOException
	 */
	public Login create(String nickName, byte[] image) throws IOException {
		FileChannel channel = null;
		FileLock locked = null;
		try {

			// create lock
			channel = FileChannel.open(loginFile.toPath(), StandardOpenOption.WRITE, StandardOpenOption.READ,
					StandardOpenOption.READ);
			locked = channel.lock();

			// 创建新用户
			Login user = new Login();
			user.setId(IDGens.genUserID());
			user.setNickName(StringUtils.isBlank(nickName) ? NicknameGens.getNickName() : nickName);
			user.setHeadimg(image);

			// 登录数据
			List<Login> users = this.users(channel);
			users.add(user);

			// store users
			byte[] bytes = SERIALIZER.serialize(users);
			channel.write(ByteBuffer.wrap(bytes));

			// lock user
			if (!this.tryLogin(user)) {
				throw new SubjectLockException("用户登录异常");
			}

			// return new user
			return user.setLock(true);
		} finally {
			try {
				locked.close();
			} catch (Exception e) {
			}
			try {
				channel.close();
			} catch (Exception e) {
			}
		}
	}

	/**
	 * 系统的登录记录
	 * 
	 * @return
	 */
	public List<Login> users() throws IOException {
		FileChannel channel = null;
		FileLock locked = null;
		try {

			// create lock
			channel = FileChannel.open(loginFile.toPath(), StandardOpenOption.WRITE, StandardOpenOption.READ,
					StandardOpenOption.READ);
			locked = channel.lock();

			// load users
			List<Login> users = this.users(channel);

			// check user login
			for (Login user : users) {
				user.setLock(this.isLogin(user));
			}

			// return users
			return users;
		} finally {
			try {
				locked.close();
			} catch (Exception e) {
			}
			try {
				channel.close();
			} catch (Exception e) {
			}
		}
	}

	@SuppressWarnings("unchecked")
	private List<Login> users(FileChannel channel) throws IOException {
		// load users
		byte[] bytes = FileUtils.read(channel);
		List<Login> users = (List<Login>) SERIALIZER.deserialize(bytes);
		if (users == null) {
			users = Lists.newArrayList();
		}

		// check user login
		for (Login user : users) {
			user.setLock(this.isLogin(user));
		}

		// return users
		return users;
	}

	public LoginService addChangeListener(ChangeListener listener) {
		listener.change(login);
		this.changes.add(listener);
		return this;
	}

	public LoginService removeChangeListener(ChangeListener listener) {
		this.changes.remove(listener);
		return this;
	}

	public LoginService fireChangeListeners() {
		this.changes.stream().forEach(c -> c.change(login));
		return this;
	}

	public static LoginService me() {
		try {
			if (Instance == null) {
				synchronized (Login.class) {
					if (Instance == null) {
						Instance = new LoginService();
					}
				}
			}
		} catch (Exception e) {
		}
		return Instance;
	}

	public static void logout() {
		try {
			if (me().loginLock != null) {
				me().loginLock.release();
			}
		} catch (Exception e) {
		}
	}

	public static Login login() {
		Login login = null;
		try {
			login = me().login;
		} catch (Exception e) {
		}
		return login;
	}

	@FunctionalInterface
	public static interface ChangeListener {
		void change(Login login);
	}
}
